/*
 *  Copyright 2008 The MITRE Corporation (http://www.mitre.org/). All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package org.mitre.lattice.lattice;

import java.io.Serializable;
import java.util.ArrayList;

import org.mitre.mrald.util.Config;
/**
 *  Description of the Class
 *
 *@author     Todd Cornett, tcornett@mitre.org, The MITRE Corporation
 *@created    June 5, 2003
 *@see        LatticeNode
 *@version    1.0
 */
public class LatticeTree extends Object implements LatticeTreeInterface, Serializable
{

	/*
	 *  This node is the root of the tree.  It represent the lowest
	 *  possible COI in the relation.,
	 */
	private LatticeNode root_node = null;

	/**
	 *  Description of the Field
	 */
	public static String ROOT_LABEL = "Public";


	/**
	 *  Constructs a new LatticeTree instance with the new root node, whose name is
	 *  default to 'Public'.
	 *
	 *@since    1.0
	 */
	public LatticeTree()
	{
		this.root_node = Config.getLatticeFactory().createNode( ROOT_LABEL);
	}


	/**
	 *  Constructs a new LatticeTree instance with the root node's name equal to
	 *  the specific name and the children and parents of the root node are equal
	 *  to null.
	 *
	 *@param  root_name  The name of the root node of the new LatticeTree
	 *@since             1.0
	 */
	public LatticeTree(String root_name)
	{
		this.root_node = Config.getLatticeFactory().createNode(root_name);
	}


	/**
	 *  Constructs a new LatticeTree instance with the root node equal to the
	 *  specific LatticeNode, includes the name, children and parents of the
	 *  LatticeNode.
	 *
	 *@param  root_node  The LatticeNode set to the root node of the new
	 *      LatticeTree
	 *@since             1.0
	 */
	public LatticeTree(LatticeNode root_node)
	{
		this.root_node = root_node;
	}


	/**
	 *  Constructs a LatticeTree instance equals to the passed LatticeTree object
	 *
	 *@param  newTree  The LatticeTree argument that will be copied to the new
	 *      LatticeTree
	 *@since           1.0
	 */
	public LatticeTree(LatticeTree newTree)
	{
		this.root_node = newTree.getRootNode();
	}


	/**
	 *  Returns the LatticeNode object of the root node of the LatticeTree object
	 *
	 *@return     The rootNode value
	 *@returns    The LatticeNode object that represents the root node of the
	 *      LatticeTree
	 *@since      1.0
	 */
	public LatticeNode getRootNode()
	{
		return root_node;
	}

	/**
	 *  Returns the LatticeNode object of the root node of the LatticeTree object
	 *
	 *@return     The rootNode value
	 *@returns    The LatticeNode object that represents the root node of the
	 *      LatticeTree
	 *@since      1.0
	 */
	public void setRootNode(LatticeNode thisNode)
	{
		root_node = thisNode;
	}


	/**
	 *  Returns the LatticeNode object of the root node of the LatticeTree object
	 *  in an array of LatticeNode
	 *
	 *@return     The rootNodeArray value
	 *@returns    The LatticeNode array that represents the root node of the
	 *      LatticeTree
	 *@since      1.0
	 */
	public LatticeNode[] getRootNodeArray()
	{
		LatticeNode[] ret = {root_node};
		return ret;
	}


	/**
	 *  Returns the LatticeNode object of the root node of the LatticeTree object
	 *  in an ArrayList
	 *
	 *@return     The rootNodeArrayList value
	 *@returns    The ArrayList of the LatticeNode that represents the root node of
	 *      the LatticeTree
	 *@since      1.0
	 */
	public ArrayList<LatticeNode> getRootNodeArrayList()
	{
		ArrayList<LatticeNode> ret = new ArrayList<LatticeNode>();
		ret.add(root_node);
		return ret;
	}


	/**
	 *  Adds a node to the tree with the specified name, the children specified by
	 *  the argument nodeParents, and the parents specified by the argument
	 *  nodeChildren. By default, the new node gets the root node added to its
	 *  children.
	 *
	 *@param  name          The name of the new node
	 *@param  nodeParents   The ArrayList of children for the new node
	 *@param  nodeChildren  The ArrayList of parents for the new node
	 *@return               Description of the Return Value
	 *@returns              true if the node is added successfully; false otherwise
	 *@since                1.0
	 */
	public boolean addNode(String name, ArrayList<LatticeNode> nodeParents, ArrayList<LatticeNode> nodeChildren) throws InvalidLatticeStructureException
	{
		nodeChildren.add(root_node);
		LatticeNode temp = Config.getLatticeFactory().createNode(name, nodeChildren, nodeParents);
		root_node.addParent(temp);
		return true;
	}

        /*  Adds a node to the tree with the specified name, the children set to null
	 *  and the parents set to null. By default, the children of the new node are
	 *  set to the root node.
	 *
	 *@param  name  The name of the new node
	 *@return       Description of the Return Value
	 *@returns      true if the node is added successfully; false otherwise
	 *@since        1.0
	 */
	public boolean addNode(LatticeNode newNode) throws InvalidLatticeStructureException
	{
	    ArrayList<LatticeNode> children = new ArrayList<LatticeNode>();
	    children.add(root_node);
            newNode.addChildren( children );

	    root_node.addParent( newNode);
	    if (root_node.countParentNodes() > 0)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	/**
	 *  Adds a node to the tree with the specified name, the children set to null
	 *  and the parents set to null. By default, the children of the new node are
	 *  set to the root node.
	 *
	 *@param  name  The name of the new node
	 *@return       Description of the Return Value
	 *@returns      true if the node is added successfully; false otherwise
	 *@since        1.0
	 */
	public boolean addNode(String name) throws InvalidLatticeStructureException
	{

		LatticeNode newNode = Config.getLatticeFactory().createNode(name);
		newNode.addChild( root_node);

		//Add the root Node to the Children of the new Node.
		if (!root_node.addParent( newNode))
		{
			return false;
		}

		if (root_node.countParentNodes() > 0)
		{
			return true;
		}
		else
		{
			return false;
		}
	}


	/**
	 *  Checks to ensure the tree is a valid tree (i.e. the root node has no
	 *  children)
	 *
	 *@return     Description of the Return Value
	 *@returns    true if the tree is valid; false otherwise
	 *@since      1.0
	 */
	public boolean checkTree()
	{

		if (root_node.getChildren() != null)
		{
			return false;
		}
		else
		{
			return true;
		}
	}


	/**
	 *  Counts the number of nodes in the tree, using the principal that all nodes
	 *  have a direct link to the root node of the tree.
	 *
	 *@return     Description of the Return Value
	 *@returns    The integer number of the nodes of the tree.
	 *@since      1.0
	 */
	public int countNodes()
	{
		return root_node.getParents().size();
	}

	/**
	 *  Checks to see if any parents that currently exist
	 * for this node are currently part of the new Node being added.
	 *
	 *@param  parent  The specified parent to be added to the parents of this
	 *      LatticeNode
	 *@return         true if the parent is removed successfully; false otherwise
	 */
	public void removeCyclic(LatticeNode childNode, LatticeNode newNode) throws InvalidLatticeStructureException
	{
	    ArrayList dominatesNewNode = dominates(newNode);

	    ArrayList childParents = childNode.getParents();

	    for (int i= childParents.size(); i > 0; i--  )
	    {
                LatticeNode childParent = (LatticeNode)childParents.get(i-1);
		if (dominatesNewNode.contains(childParent))
		{

			childNode.removeParent(childParent);
			childParent.removeChild(childNode);
		}
	    }
	}


	/**
	 *  Checks to see if any parents that currently exist
	 * for this node are currently part of the new Node being added.
	 *
	 *@param  parent  The specified parent to be added to the parents of this
	 *      LatticeNode
	 *@return         true if the parent is removed successfully; false otherwise
	 */
	public void removeCyclicChild(LatticeNode parentNode, LatticeNode newNode) throws InvalidLatticeStructureException
	{
	    ArrayList childOfNewNode = children(newNode);

	    ArrayList parentChildren = parentNode.getChildren();

	    for (int i= parentChildren.size(); i > 0; i--  )
	    {
                LatticeNode parentChild = (LatticeNode)parentChildren.get(i-1);
		if (childOfNewNode.contains(parentChild))
		{
			parentNode.removeChild(parentChild);
			parentChild.removeParent(parentNode);
		}
	    }
	}

	/**
	 *@param  keyNode  Description of the Parameter
	 *@return          Description of the Return Value
	 *@returns         The integer number of the nodes of the tree.
	 *@since           1.0
	 */
	public ArrayList dominates(LatticeNode keyNode)
	{
		ArrayList<LatticeNode> dominatesList = new ArrayList<LatticeNode>();

		//If this is the root node that is being specified as a key node. Then add to the
		//list. As retrieving all in Lattice.
		if (keyNode == root_node)
		{
			dominatesList.add(keyNode);
		}

		dominatesList = getDominatesList(keyNode, dominatesList, true);

		return dominatesList;
	}


	/**
	 *  Checks to see if the new Parent being added to the current node, currently
	 *  is part of the Child Heirachy of this node. If it is then a true will
	 *  result. Stopping the node from being added. (Possibly use a
	 *  InvalidChildParentException?)
	 *
	 *@param  currentNode  Description of the Parameter
	 *@param  addNode      Description of the Parameter
	 *@return              The child value
	 *@returns             The integer number of the nodes of the tree.
	 *@since               1.0
	 */
	public boolean isChild(LatticeNode currentNode, LatticeNode addNode)
	{
		ArrayList childList = children(currentNode);

		if (childList.contains(addNode))
		{
			return true;
		}

		return false;
	}


	/**
	 *  (Possibly use a InvalidChildParentException?)
	 *
	 *@param  currentNode  Description of the Parameter
	 *@param  keyNode      Description of the Parameter
	 *@return              The directChild value
	 *@returns             The integer number of the nodes of the tree.
	 *@since               1.0
	 */
	public boolean isDirectChild(LatticeNode currentNode, LatticeNode keyNode)
	{
		if (keyNode.getChildren().contains(currentNode))
		{
			return true;
		}
		return false;
	}


	/**
	 *  (Possibly use a InvalidChildParentException?)
	 *
	 *@param  currentNode  Description of the Parameter
	 *@param  addNode      Description of the Parameter
	 *@return              The directParent value
	 *@returns             The integer number of the nodes of the tree.
	 *@since               1.0
	 */
	public boolean isDirectParent(LatticeNode currentNode, LatticeNode addNode)
	{
		return false;
	}


	/**
	 *@param  keyNode  Description of the Parameter
	 *@return          Description of the Return Value
	 *@returns         The integer number of the nodes of the tree.
	 *@since           1.0
	 */
	public ArrayList<LatticeNode> children(LatticeNode keyNode)
	{
		ArrayList<LatticeNode> childList = new ArrayList<LatticeNode>();
		return getChildrenList(keyNode, childList, true);
	}


	/**
	 *  Counts the number of nodes in the tree, using the principal that all nodes
	 *  have a direct link to the root node of the tree.
	 *
	 *@param  keyNode    Description of the Parameter
	 *@param  oldList    Description of the Parameter
	 *@param  downLevel  Description of the Parameter
	 *@return            The dominatesList value
	 *@returns           The integer number of the nodes of the tree.
	 *@since             1.0
	 */
	private ArrayList<LatticeNode> getDominatesList(LatticeNode keyNode, ArrayList<LatticeNode> oldList, boolean downLevel)
	{
		try
		{
			ArrayList<LatticeNode> parentList = oldList;
			ArrayList<LatticeNode> parents = keyNode.getParents();

//			int parentIndex = parentList.size();
			for (int i = 0; i < parents.size(); i++)
			{
				LatticeNode newNode = parents.get(i);
				if (!parentList.contains(newNode))
				{
					parentList.add(newNode);
				}

				if ((newNode.getParents().size() > 0) && (downLevel))
				{
					parentList = getDominatesList(newNode, parentList, downLevel);
				}

			}

			return parentList;
		}
		catch (Exception e)
		{
			//MraldOutFile.logToFile(Config.getProperty("LOGFILE"), e.getStackTrace());
		}
		return null;
	}


	/**
	 *  Counts the number of nodes in the tree, using the principal that all nodes
	 *  have a direct link to the root node of the tree.
	 *
	 *@param  keyNode    Description of the Parameter
	 *@param  oldList    Description of the Parameter
	 *@param  downLevel  Description of the Parameter
	 *@return            The childrenList value
	 *@returns           The integer number of the nodes of the tree.
	 *@since             1.0
	 */
	private ArrayList<LatticeNode> getChildrenList(LatticeNode keyNode, ArrayList<LatticeNode> oldList, boolean downLevel)
	{
		ArrayList<LatticeNode> childList = oldList;
		ArrayList children = keyNode.getChildren();


		for (int i = 0; i < children.size(); i++)
		{
			LatticeNode newNode = (LatticeNode) children.get(i);
			if (!childList.contains(newNode))
			{
				childList.add(newNode);
			}

			if ((newNode.getParents().size() > 0) && downLevel)
			{
				getChildrenList(newNode, childList, downLevel);
			}
		}
		return childList;
	}


	/**
	 *  Description of the Method
	 *
	 *@param  testTree  Description of the Parameter
	 *@return           Description of the Return Value
	 */
	public boolean equals(LatticeTree testTree)
	{
		return false;
	}


	/**
	 *  Description of the Method
	 *
	 *@return    Description of the Return Value
	 */
	public LatticeTree exportTree()
	{
		return new LatticeTree();
	}


	/**
	 *  Description of the Method
	 *
	 *@param  testTree  Description of the Parameter
	 *@return           Description of the Return Value
	 */
	public int hash(LatticeTree testTree)
	{
		return 0;
	}


	/**
	 *  Description of the Method
	 *
	 *@param  keyTree  Description of the Parameter
	 *@return          Description of the Return Value
	 */
	public boolean importTree(LatticeTree keyTree)
	{
		return false;
	}


	/**
	 *  Description of the Method
	 *
	 *@param  name  Description of the Parameter
	 *@return       Description of the Return Value
	 */
	public boolean removeNode(String name)
	{

		//LatticeNode removeNode = searchTree(name);
		//Get all Parents. Remove from children
		//Get all Children. Remove from Parents
		return false;
	}


	/**
	 *  Starting from the root node , search up through the lattice until the
	 *  requested node is located. The GetDominates will return all nodes as this
	 *  is the most public node Return this node.
	 *
	 *@param  keyNode                    Description of the Parameter
	 *@return                            Description of the Return Value
	 *@exception  NodeNotFoundException  Description of the Exception
	 */
	public LatticeNode[] searchTree(LatticeNode keyNode)
		throws NodeNotFoundException
	{

		if (root_node == null)
		{
			//MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "Searching tree. Root node not set ");
		}
		ArrayList parents = dominates(root_node);

		if (parents.contains(keyNode))
		{
			return new LatticeNode[]{keyNode};
		}

		//If just the root node  then return this
		if ( keyNode.equals( root_node))
			return new LatticeNode[]{root_node};


		NodeNotFoundException notFound = new NodeNotFoundException("No node of name " + keyNode.getName() + " found");
		throw notFound;
	}


	/**
	 *  Starting from the root node , search up through the lattice until the
	 *  requested node is located. Return this node.
	 *
	 *@param  nodeName                   Description of the Parameter
	 *@return                            Description of the Return Value
	 *@exception  NodeNotFoundException  Description of the Exception
	 */
	public LatticeNode searchTree(String nodeName)
		throws NodeNotFoundException
	{

		if (root_node == null)
		{
			//MraldOutFile.logToFile(Config.getProperty("LOGFILE"), "Searching tree. Root node not set ");
		}
		ArrayList parents = dominates(root_node);
		String thisNodeName = null;

		for (int i = 0; i < parents.size(); i++)
		{
			thisNodeName = ((LatticeNode) parents.get(i)).getName();
			if (nodeName.equals(thisNodeName))
			{
				return (LatticeNode) parents.get(i);
			}
		}


		NodeNotFoundException notFound = new NodeNotFoundException("LATT-004", "\nDetails : Group " + nodeName + " not found. ");
		throw notFound;
	}

}

